/*
   Copyright (C) 2011 Roman Belov <romblv@gmail.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "sim/edsim.h"
#include "sim/args.h"
#include "lang/parser.h"
#include "lang/signal.h"
#include "lib/base.h"
#include "util/list.h"
#include "util/link.h"
#include "util/rand.h"

struct edsim_global glob;

static int
edsim_link_pull(struct link *ln, struct port *p, int val)
{
	int		touch = 0;

	if (ln->master && ln->master != p) {
		fprintf(stderr, "runtime: \"%s/%s.%s\" attempts to take link over but \"%s/%s.%s\""
				" port still holds it, likely short circuit\n",
				p->owner->id, p->owner->lid, p->id,
				ln->master->owner->id, ln->master->owner->lid, ln->master->id);
		exit(EXIT_FAILURE);
	}

	if (ln->master == NULL) {
		ln->master = p;
		touch += 1;
	}

	if (ln->val != val) {
		ln->pval = val;
		touch += 1;
	}

	return touch;
}

static int
edsim_link_unpull(struct link *ln, struct port *p)
{
	int		touch = 0;

	if (ln->master == p) {
		ln->master = NULL;
		touch += 1;
	}

	return touch;
}

void
edsim_lib_share_in(struct module *m, int *arg, int n)
{
	struct link	*ln;
	int		i;

	for (i = 0; i < n; ++i) {
		ln = m->lmap[i]->lne;
		if (ln) arg[i] = ln->val;
		else arg[i] = SIGNAL_PULL_DOWN;
	}
}

int
edsim_lib_share_out(struct module *m, int i, int val)
{
	struct port	*p;
	struct link	*ln;
	int		retval = 0;

	p = m->lmap[i];
	ln = p->lne;

	if (ln) retval = edsim_link_pull(ln, p, val);

	return retval;
}

int
edsim_lib_share_unpull(struct module *m, int i)
{
	struct port	*p;
	struct link	*ln;
	int		retval = 0;

	p = m->lmap[i];
	ln = p->lne;

	if (ln) retval = edsim_link_unpull(ln, p);

	return retval;
}

void
edsim_lib_share_spec_fail_report(struct module *m, struct spec *s)
{
	fprintf(stderr, "runtime: the \"%s/%s\" module has incorrect specify value"
			" \"%s\" = \"%s\"\n",
			m->id, m->lid, s->id,  s ? s->id : "NULL");
	exit(EXIT_FAILURE);
}

/* The function is a core of simulation process. Each hight level module (it
 * means that module is constructed from another ones) uses it as own calc
 * function.
 *
 * Simulation process is iterative. In order to detect how early we may stop
 * the touch values are introduced. During simulation touch value are
 * calculated every iteration basing on the changes of signals. If the port
 * pulls the link up/down and it takes actual effect (link may already has the
 * same state) the touch value will be increased. If so, it means that we have
 * some changes and need to recalculate module. We repeat iteration until touch
 * value becomes zero.
 *
 */

int
edsim_calc(struct module *m)
{
	struct module	*l;
	struct port	*p;
	struct link	*ln;
	int		touch = 0;

	list_for_each(&m->port, p) {

		if (p->lni == NULL || p->lne == NULL)
			continue;

		if (p->bits & SIGNAL_BIT_IN) {

			if (p->lne->master)
				touch += edsim_link_pull(p->lni, p, p->lne->val);
			else
				touch += edsim_link_unpull(p->lni, p);
		}

		if (p->bits & SIGNAL_BIT_OUT) {

			if (p->lni->master)
				touch += edsim_link_pull(p->lne, p, p->lni->val);
			else
				touch += edsim_link_unpull(p->lne, p);
		}
	}

	list_for_each(&m->local, l) {
		touch += l->calc(l);
	}

	list_for_each(&m->link, ln) {
		ln->val = ln->pval;
	}

	return touch;
}

static void
edsim_propagation(struct module *m, int lim)
{
	int		it = 0;
	int		touch;

	do {
		touch = m->calc(m);

		if (touch == 0) break;

		if (++it > lim) {
			fprintf(stderr, "runtime: unable to simulate the circuit due to"
					" very hight propagation delay or instability\n");
			exit(EXIT_FAILURE);
		}
	}
	while (1);
}

void
edsim_simulate(const char *file)
{
	struct parser	*ps;
	struct module	*sim, *m;
	link_node_t	*lk;
	int		iter = 0;

	lib_base_set_default(edsim_calc);

	ps = parser_open(file, NULL, NULL, 0);

	if (ps == NULL) return;

	sim = module_find(ps, args.sim_id);

	if (sim == NULL) {
		fprintf(stderr, "runtime: simulation module \"%s\" does not exist\n",
				args.sim_id);
		exit(EXIT_FAILURE);
	}

	glob.run_tick = 0;
	list_up(&glob.def_list);

	do {
		glob.def_call = 0;

		edsim_propagation(sim, args.pro_limit);

		/* Deferred calls are necessary to do some job which should be
		 * done once per global tick, like printouts.
		 * */

		glob.def_call = 1;

		list_for_each(&glob.def_list, lk) {

			m = lk->pl;
			m->calc(m);
		}

		++glob.run_tick;

		if (args.sim_limit && ++iter == args.sim_limit) break;
	}
	while (1);

	parser_close(ps);
}

